/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 *
 */

import path from 'path';

import { FileSet, MetricLogType, TaskInputValue } from '@ohos/hvigor';
import * as fs from 'fs-extra';

import { BuildDirConst } from '../const/build-directory-const.js';
import { CommonConst, DefaultTargetConst } from '../const/common-const.js';
import { ModuleType } from '../enum/module-type-enum.js';
import { NativeLibraryModel } from '../model/cxx/native-library-model.js';
import { ExternalNativeOpt, NapiLibFilterOpt } from '../options/build/build-opt.js';
import { Dependency } from '../project/dependency/core/dependency-interface.js';
import { DependencyManager } from '../project/dependency/dependency-manager.js';
import { CmakeUtil } from '../utils/cmake-util.js';
import { FileUtil } from '../utils/file-util.js';
import { HarTargetUtil } from '../utils/har-target-util.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';
import { BuildNativeWithNinja } from './build-native-with-ninja.js';
import { ProcessLibsCommonProperties } from './common-properties/process-libs-common-properties.js';
import { TaskNames } from './common/task-names.js';
import { TargetTaskService } from './service/target-task-service.js';
import { buildLibs, LibWithPkgInfo, processLibs, ProcessLibsOptions } from './worker/process-libs.js';

import Task = TaskNames.Task;

/**
 * 收集hap和har依赖中的.so文件
 *
 * @since 2022/1/20
 */
export class ProcessLibs extends ProcessLibsCommonProperties {
  private logger: OhosLogger = OhosLogger.getLogger(ProcessLibs.name);

  private localOutput: string | undefined;
  private libsOutputDir: string | undefined;
  private _nativeOption?: ExternalNativeOpt;
  private filterRule: NapiLibFilterOpt | undefined;
  private allHarLibs: Map<string, Dependency> = new Map();
  private libsWithPkg: LibWithPkgInfo[] = [];
  private hasLocalNativeOutput: boolean | undefined;
  private collectAllLibs: boolean | undefined;
  private readElf: string | undefined;
  private compileCommandsPath: string[] | undefined;
  // 仓颉构建产物
  private cangjieLibs: string | undefined;
  private localOutputHash?: Map<string, string>;
  private isHarType: boolean | undefined;
  private nativeOptionArg: string | string[] | undefined;
  private nativeLibs: Map<string, NativeLibraryModel>[] | undefined;

  taskShouldDo(): boolean {
    if (this.moduleModel.isAtomicService()) {
      fs.ensureDirSync(this.pathInfo.getIntermediatesProcessLibs());
    }
    return !this.moduleModel.isAtomicService();
  }

  async beforeAlwaysAction(): Promise<void> {
    this.init();
    if (this.service.isFaMode()) {
      this.allHarLibs = this.getAllHarLibs();
      this.libsWithPkg = this.getHarLibsWithPkg();
    }
    if (this.hasLocalNativeOutput && this.localOutput) {
      this.localOutputHash = await FileUtil.hashEntry(this.localOutput);
    }
  }

  declareInputs(): Map<string, TaskInputValue> {
    const inputs = super.declareInputs();
    if (this.filterRule !== undefined) {
      Object.entries(this.filterRule)
        .filter(([, val]) => !!val)
        .forEach(([key, val]) => inputs.set(key, val as string[] | boolean));
    }

    if (this.hasLocalNativeOutput && !!this.localOutputHash) {
      this.localOutputHash.forEach((val, key) => {
        if (this.localOutput) {
          inputs.set(path.relative(this.localOutput, key), val);
        }
      });
    }

    inputs
      .set('isBundledDependencies', this.isBundledDependencies)
      .set('isByteCodeHar', this.targetService.isByteCodeHar())
      .set('excludeFromHar', this.targetService.getNativeLibOption().excludeFromHar ?? true);
    return inputs;
  }

  declareInputFiles(): FileSet {
    const fileSet: FileSet = new FileSet();
    const hapLocalLibs = path.resolve(this.moduleModel.getProjectDir(), BuildDirConst.LIBS);
    if (fs.existsSync(hapLocalLibs)) {
      fileSet.addEntry(hapLocalLibs, { isDirectory: true });
    }
    for (const key of this.allHarLibs.keys()) {
      if (fs.existsSync(key)) {
        fileSet.addEntry(key);
      }
    }
    if (fs.existsSync(this.projectModel.getProfilePath())) {
      fileSet.addEntry(this.projectModel.getProfilePath());
    }

    if (fs.existsSync(this.moduleModel.getProfilePath())) {
      fileSet.addEntry(this.moduleModel.getProfilePath());
    }

    // 如果仓颉构建产物路径存在，则做为增量编译输入
    if (this.cangjieLibs && fs.existsSync(this.cangjieLibs)) {
      fileSet.addEntry(this.cangjieLibs, { isDirectory: true });
    }
    return fileSet;
  }

  declareOutputFiles(): FileSet {
    if (!this.libsOutputDir) {
      return new FileSet();
    }
    return new FileSet().addEntry(this.libsOutputDir, { isDirectory: true });
  }

  constructor(taskService: TargetTaskService) {
    super(taskService, Task.PROCESS_LIB);
  }

  /**
   * 由于再构造函数中执行逻辑如果此任务的对象被提前实例化会导致某些意外现象，例如遗漏了某些so未收集
   * 此处就将构造函数中的初始化变量转移到init中
   * @private
   */
  private init() {
    const taskService = this.targetService;
    const pathInfo = taskService.getTargetData().getPathInfo();
    const abiFilters = CmakeUtil.checkAbiFilters(taskService.getBuildOption().externalNativeOptions?.abiFilters, taskService.getTargetData().isHarmonyOS(), this.moduleModel, this.targetName);
    this.collectAllLibs = !!taskService.getBuildOption().nativeLib?.collectAllLibs;
    this.allHarLibs = this.getAllHarLibs();
    this.libsWithPkg = this.getHarLibsWithPkg();
    this.libsOutputDir = pathInfo.getIntermediatesProcessLibs();
    this.localOutput = pathInfo.getIntermediatesCppOutPut();
    this._nativeOption = taskService.getBuildOption().externalNativeOptions;
    this.hasLocalNativeOutput = CmakeUtil.nativeTaskCondition(this.moduleModel, this.targetData, this._nativeOption);
    this.filterRule = taskService.getNativeLibOption().filter;
    this.readElf = taskService.getSdkInfo().getSdkLlvmReadElf();
    this.compileCommandsPath = abiFilters.map((abiFilter) =>
      path.resolve(pathInfo.getNinjaWorkDir(), abiFilter, CommonConst.COMPILE_COMMANDS_JSON),
    );
    if (this.hasLocalNativeOutput){
      this.nativeLibs = abiFilters.map((abiFilter) =>
        CmakeUtil.parseLibraries(path.resolve(pathInfo.getNinjaWorkDir(), abiFilter), this.targetName, abiFilter),
      );
    }

    // 仓颉构建产物路径
    this.cangjieLibs = path.resolve(pathInfo.getIntermediatesCangjieOutPut());
    this.isHarType = this.moduleModel.getModuleType() === ModuleType.Har;
    this.nativeOptionArg = this._nativeOption?.arguments;
  }

  get isBundledDependencies(): boolean {
    return this.targetService.isBundledDependencies();
  }

  initTaskDepends() {
    this.declareDepends(BuildNativeWithNinja.name);
  }

  protected async doTaskAction(): Promise<void> {
    const self = this.service.getDependencyInfo().getSelfAsDependency();
    const localLibsPath = path.resolve(this.moduleModel.getProjectDir(), 'libs');
    const outputLibsPath = this.pathInfo.getIntermediatesCppOutPut();
    if (!this.libsOutputDir || !this.cangjieLibs) {
      // 不可能进入此if, 这两个属性都是再最开始初始化的
      this.logger.warn('processLibs task error: this.libsOutputDir or this.cangjieLibs is undefined');
      return;
    }
    const processLibsOptions: ProcessLibsOptions = {
      allHarLibs: this.libsWithPkg,
      outputDir: this.libsOutputDir,
      filterRule: this.filterRule,
      localLibs: buildLibs(this.getLibsInfo(localLibsPath), self, localLibsPath),
      outputLibs: buildLibs(this.getLibsInfo(outputLibsPath), self, outputLibsPath),
      profilePath: CmakeUtil.getMayBeOccurErrorsFilePath(this.moduleModel),
      moduleName: this.moduleName,
      cangjieLibs: buildLibs(this.getLibsInfo(this.cangjieLibs), self, this.cangjieLibs),
      collectAllLibs: this.collectAllLibs,
      readElf: this.readElf,
      localOutputDir: this.localOutput,
      compileCommandsPath: this.compileCommandsPath,
      isHarType: this.isHarType,
      nativeOptionArg: this.nativeOptionArg,
      nativeLibs: this.nativeLibs,
    };

    const executeTaskName = 'process libs';
    const subExecuteDurationEvent = this.durationEvent.createSubEvent(executeTaskName, '');
    subExecuteDurationEvent.start();
    this.logger.debug('Worker dispatch failed, running processLibs on main thread.');
    await processLibs(processLibsOptions);
    subExecuteDurationEvent.stop();
    subExecuteDurationEvent.setLog(executeTaskName, MetricLogType.INFO);
  }

  private getHarLibsWithPkg() {
    const libs: LibWithPkgInfo[] = [];
    for (const [cwd, pkg] of this.allHarLibs.entries()) {
      libs.push(...buildLibs(this.getLibsInfo(cwd), pkg, cwd));
    }
    return libs;
  }

  private getAllHarLibs() {
    const pkgs = new Map<string, Dependency>();
    this.collectRemoteHarLibs(this.targetService, pkgs);

    if (this.moduleModel.isHarModule() && this.targetService.getTargetData().getTargetName() !== DefaultTargetConst.OHOS_TEST_TARGET) {
      if (!this.isBundledDependencies) {
        return pkgs;
      }

      this.service.getHarModuleDependencies().forEach((dependency) => {
        const harTargetData = HarTargetUtil.getHarTargetData(
          dependency,
          this.moduleModel,
          this.targetService.getTargetData().getTargetName(),
          this.moduleName,
          this.targetService,
        );
        const moduleModelByDependency = DependencyManager.getModuleModelByDependency(dependency[1], this.projectModel);
        if (moduleModelByDependency) {
          const depTargetService = this.projectModel.getTarget(moduleModelByDependency?.getName(), harTargetData);
          this.collectLocalHarLibs(depTargetService, pkgs);
        }
      });

      return pkgs;
    }

    // collect local libs by target
    const localHarTargets = HarTargetUtil.calDepHarTargets(this.targetService);
    for (const [moduleName, targetName] of localHarTargets) {
      const targetService = this.projectModel.getTarget(moduleName, targetName);
      this.collectLocalHarLibs(targetService, pkgs);
    }

    return pkgs;
  }

  /**
   * 收集模块本地依赖har模块的so
   * @param depTargetService
   * @param pkgs
   * @private
   */
  private collectLocalHarLibs(depTargetService: TargetTaskService | undefined, pkgs: Map<string, Dependency>): void {
    if (depTargetService) {
      const cppOutPut = depTargetService.getTargetData().getPathInfo().getIntermediatesProcessLibs();
      const selfAsDependency = depTargetService.getModuleService().getDependencyInfo().getSelfAsDependency();
      pkgs.set(cppOutPut, selfAsDependency);
      pkgs.set(selfAsDependency.getDefaultLibsDir(), selfAsDependency);
    }
  }

  /**
   * 收集模块远程依赖har模块的so
   * 若har模块bundledDependencies为true时不收集其依赖字节码har的so
   * @param targetService
   * @param pkgs
   * @private
   */
  private collectRemoteHarLibs(targetService: TargetTaskService, pkgs: Map<string, Dependency>): void {
    this.service.getRemoteHarDependencies().forEach((dependency) => {
      if (this.isBundledDependencies) {
        // 仅har模块支持配置bundledDependencies
        if (dependency.isByteCodeHarDependency()) {
          // 字节码har不收集
          return;
        }

        pkgs.set(dependency.getDefaultLibsDir(), dependency);
      } else {
        if (this.moduleModel.isHarModule() && this.targetService.getTargetData().getTargetName() !== DefaultTargetConst.OHOS_TEST_TARGET) {
          return;
        }
        pkgs.set(dependency.getDefaultLibsDir(), dependency);
      }
    });
  }
}
